import { invokeHooks, PatchFlags, ShapeFlags } from "../shared/index.js";
import { convert, Fragment, isSameVnode, Text } from "./createVNode.js";
import { ReactiveEffect } from "../reactivity/index.js";
import { queueJob } from "./scheduler.js";
import { createInstance, setupComponent } from "./component.js";
import { hasChangedProps, updateProps } from "./componentProps.js";
import { isKeepAlive } from "./keepAlive.js";

function getSeq(arr) {
  let result = [0];
  const len = arr.length; // 总长度
  let resultLastIndex;
  let start;
  let end;
  let middle = 0;
  let p = arr.slice(0).fill(0);
  for (let i = 0; i < len; i++) {
    const arrI = arr[i];
    if (arrI != 0) {
      // 获取队列中的最后一项
      resultLastIndex = result[result.length - 1];
      if (arr[resultLastIndex] < arrI) {
        result.push(i);
        p[i] = resultLastIndex;
        continue;
      }
      // ..替换
      start = 0;
      end = result.length - 1;
      while (start < end) {
        // 折半 查找
        middle = Math.floor((start + end) / 2);
        // 中间那一项的值
        // 1,2,3,4,6    5
        if (arr[result[middle]] < arrI) {
          start = middle + 1;
        } else {
          end = middle;
        }
      }
      if (arrI < arr[result[end]]) {
        p[i] = result[end - 1];
        result[end] = i; // 发现最后找到的索引比这一项大，那就用这个索引换掉，因为更有潜力
      }
    }
  }
  let i = result.length;
  let last = result[i - 1];
  while (i-- > 0) {
    result[i] = last;
    last = p[last];
  }
  return result;
}

export function createRender(renderOptions) {
  // 此方法不关心options是谁提供的
  const {
    createElement: hostCreateElement,
    createText: hostCreateText,
    remove: hostRemove,
    querySelector: hostQuerySelector,
    setElementText: hostSetElementText,
    setText: hostSetText,
    insert: hostInsert,
    createComment: hostCreateComment,
    nextSibling: hostNextSibling,
    parentNode: hostParentNode,
    patchProps: hostPatchProp,
  } = renderOptions; // 这些方法和某个平台无关

  // 挂载子节点
  const mountChildren = (children, container, parentComponent) => {
    for (let i = 0; i < children.length; i++) {
      // 递归调用patch 方法 创建元素
      const child = convert(children[i]);

      patch(null, child, container, null, parentComponent);
    }
  };

  // 卸载子节点
  const unmountChildren = (children, parentComponent) => {
    for (let i = 0; i < children.length; i++) {
      unmount(children[i], parentComponent);
    }
  };

  // 卸载节点
  const unmount = (vnode, parentComponent) => {
    const { shapeFlag } = vnode;
    // 删除节点
    if (vnode.type === Fragment) {
      // 如果是文档碎片
      return unmountChildren(vnode.children);
    }

    if (shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
      // vnode 如何找到对应的 keep-alive
      return parentComponent.ctx.deactivated(vnode);
    }

    // 如果是组件，移除subTree
    if (shapeFlag & ShapeFlags.COMPONENT) {
      const { bum, um } = vnode.component;
      bum && invokeHooks(bum);
      unmount(vnode.component.subTree, parentComponent);
      um && invokeHooks(um);
      return;
    }
    remove(vnode);
  };
  function remove(vnode) {
    const { el, transition } = vnode;
    const performRemove = () => {
      hostRemove(el);
    };
    if (transition.leave) {
      transition.leave(el, performRemove);
    } else {
      performRemove();
    }
  }

  // 挂载元素
  const mountElement = (vnode, container, anchor, parentComponent) => {
    const { type, props, shapeFlag, children } = vnode;
    // 先创建父元素
    let el = (vnode.el = hostCreateElement(type));
    // 给父元素增添属性
    if (props) {
      for (let key in props) {
        hostPatchProp(el, key, null, props[key]);
      }
    }
    // 区分子节点类型，挂载子节点
    if (ShapeFlags.ARRAY_CHILDREN & shapeFlag) {
      mountChildren(children, el, parentComponent);
    } else {
      hostSetElementText(el, children, anchor); // 将元素插入到父级
    }
    if (vnode.transition) {
      vnode.transition.beforeEnter(el);
    }
    // 插入到容器中
    hostInsert(el, container, anchor);
    if (vnode.transition) {
      vnode.transition.enter(el);
    }
  };

  // 比对属性
  const patchProps = (oldProps, newProps, el) => {
    for (const key in newProps) {
      // 用新的生效
      hostPatchProp(el, key, oldProps[key], newProps[key]);
    }
    // 老的里面有，新的没有则删除
    for (let key in oldProps) {
      if (!(key in newProps)) {
        hostPatchProp(el, key, oldProps[key], null);
      }
    }
  };

  const patchKeyedChildren = (c1, c2, el, parentComponent) => {
    // 优化的点 dom常见的操作方式 1）前后增加  前后删除
    // 如果不优化，那就比较c1, c2的差异循环即可
    // from start
    // a, b, c
    // a, b, c, d
    let i = 0; // 头部索引
    let e1 = c1.length - 1;
    let e2 = c2.length - 1;
    while (i <= e1 && i <= e2) {
      const n1 = c1[i];
      const n2 = c2[i];
      if (isSameVnode(n1, n2)) {
        patch(n1, n2, el);
      } else {
        break;
      }
      i++;
    }
    // console.log('from start', i, e1, e2) // 3 2 3

    // from end
    // a, b, c
    // d, a, b, c
    while (i <= e1 && i <= e2) {
      const n1 = c1[e1];
      const n2 = c2[e2];
      if (isSameVnode(n1, n2)) {
        patch(n1, n2, el);
      } else {
        break;
      }
      e1--;
      e2--;
    }
    // console.log('from end', i, e1, e2) // 0 -1 0

    // a, b, c      i   e1  e2
    // a, b, c, d   3   2   3
    //    a, b, c   i   e1  e2
    // d, a, b, c   0   -1  0

    // 怎么知道新的比老的多的？如何知道有新增元素？
    // 当 i 比 e1 大，说明有新增 0 > -1, 3 > 2

    // i > e1 说明新的比老的长，有新增的逻辑
    if (i > e1) {
      if (i <= e2) {
        // i - e2 之间为新增的部分
        while (i <= e2) {
          // 如果 e2 后面没有值，说明往后插入
          // 如果 e2 后面有值，说明是往前比较的，肯定是向前插入
          const nextPos = e2 + 1;
          const anchor = c2[nextPos]?.el;
          patch(null, c2[i], el, anchor); // 如何选择锚点值
          i++;
        }
      }
    } else if (i > e2) {
      // 老多新少
      while (i <= e1) {
        unmount(c1[i], parentComponent);
        i++;
      }
    }

    // console.log(i, e1, e2)
    // 未知序列对比
    let s1 = i;
    let s2 = i;
    // 将新的元素做成一个映射表，去老的里面找
    const keyToNewIndexMap = new Map();
    for (let i = s2; i <= e2; i++) {
      const child = c2[i];
      keyToNewIndexMap.set(child.key, i); // 不写key就是undefined
    }
    // 去老的里面找，看在新的里面有没有，如果没有，则说明老的被删除掉了
    const toBePatch = e2 - s2 + 1;
    const newIndexToOldIndexMap = new Array(toBePatch).fill(0);
    // 数组里面映射着老的关系
    for (let i = s1; i <= e1; i++) {
      const child = c1[i];
      const newIndex = keyToNewIndexMap.get(child.key);
      if (newIndex == undefined) {
        // 老的被删掉了
        unmount(child, parentComponent);
      } else {
        newIndexToOldIndexMap[newIndex - s2] = i + 1; // 0表示新增
        // 老的里面有新的里面也有，需要做diff算法
        // 比较这两个节点的属性差异和儿子的区别
        patch(child, c2[newIndex], el); // 只比较了属性，还需要移动位置
      }
    }

    const cressingIndexMap = getSeq(newIndexToOldIndexMap);
    let lastIndex = cressingIndexMap.length - 1;
    // 哪些元素不需要移动
    // 根据标记找到哪些索引不需要动, 倒序循环时匹配到索引跳过即可
    // [5,3,4,0] => 索引[1,2]不需要动
    // [5,3,8,0,4,6,7] => 索引 [1,4,5,6] 不需要动。0是新增
    // 递增子序列，不用非要连续
    // 我如何知道哪些元素是新增的哪些是要移动的
    // 倒序插入 (insertBefore向前插入，所以要倒序插入)

    for (let i = toBePatch - 1; i >= 0; i--) {
      const anchorIndex = s2 + i;
      const child = c2[anchorIndex];
      const insertAnchor = c2[anchorIndex + 1]?.el;
      if (newIndexToOldIndexMap[i] === 0) {
        patch(null, child, el, insertAnchor);
      } else {
        // 说明这个虚拟节点创建过
        // 暴力倒序插入
        if (cressingIndexMap[lastIndex] === i) {
          // 计算了递增子序列不用移动的部分,减少性能消耗
          lastIndex--;
        } else {
          hostInsert(child.el, el, insertAnchor);
        }
      }
    }
  };
  // 比对子节点
  const patchChildren = (n1, n2, el, parentComponent) => {
    // 比较前后2个节点的差异
    let c1 = n1.children; // 老子节点
    let c2 = n2.children; // 新子节点

    let prevShapeFlag = n1.shapeFlag;
    let shapeFlag = n2.shapeFlag;

    // 文本，数组，空组合， 9种情况

    // 第一种情况
    // 1. 文本-》数组   文本删掉，换成数组

    // 第二种情况  2，3两者逻辑相同
    // 2. 文本-》空    老文本换成空，
    // 3. 文本-》文本   老文本换成新文本

    // 第三种情况 4，5两者逻辑相同
    // 4. 数组-》文本   移除数组，换成文本
    // 5. 数组-》空     移除数组，换成空

    // 第四种情况
    // 6. 数组-》数组  （核心diff算法）

    // 第五种情况 7，8两者逻辑相同
    // 7. 空-》文本    更新文本
    // 8. 空-》数组    挂载数组

    // 第六种情况
    // 9. 空-》空      无需处理

    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        // 新的是文本，老的是数组。移除老的，换新的
        unmountChildren(c1);
      }

      // 新的是文本，老的是文本或者空，则直接采用新的
      if (c1 !== c2) {
        // 文本有变化
        hostSetElementText(el, c2);
      }
    } else {
      // 老的是数组
      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
          // 新的是数组
          // diff算法
          // console.log('diff')
          patchKeyedChildren(c1, c2, el, parentComponent);
        } else {
          // 老的是数组，新的是空
          unmountChildren(c1);
        }
      } else {
        // 老的是文本
        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
          hostSetElementText(el, "");
        }
        // 新的是数组，则直接挂载即可
        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
          mountChildren(c2, el, parentComponent);
        }
      }
    }

    // console.log(c1, c2)
  };
  const patchElement = (n1, n2, container, parentComponent) => {
    // 更新逻辑，两个元素一样。
    // 元素需要复用dom
    let el = (n2.el = n1.el);

    if (n2.patchFlag > 0) {
      // 可以靶向更新
      if (n2.patchFlag & PatchFlags.TEXT) {
        if (n1.children != n2.children) {
          hostSetElementText(el, n2.children);
        }
      }
    } else {
      patchProps(n1.props || {}, n2.props || {}, el);
    }

    if (n2.dynamicChildren) {
      patchBlockChildren(n1, n2, el, parentComponent);
    } else {
      patchChildren(n1, n2, el, parentComponent);
    }
  };

  function patchBlockChildren(n1, n2, el, parentComponent) {
    for (let i = 0; i < n2.dynamicChildren.length; i++) {
      patchElement(
        n1.dynamicChildren[i],
        n2.dynamicChildren[i],
        el,
        parentComponent
      );
    }
  }

  function processElement(n1, n2, container, anchor, parentComponent) {
    if (n1 == null) {
      // 初始化逻辑
      mountElement(n2, container, anchor, parentComponent);
    } else {
      // 更新逻辑
      patchElement(n1, n2, container, parentComponent);
    }
  }

  function processText(n1, n2, container) {
    if (n1 == null) {
      hostInsert((n2.el = hostCreateText(n2.children)), container);
    } else {
      let el = (n2.el = n1.el);
      if (n2.children != n1.children) {
        hostSetText(el, n2.children);
      }
    }
  }

  function processFragment(n1, n2, container, parentComponent) {
    if (n1 == null) {
      mountChildren(n2.children, container, parentComponent);
    } else {
      patchChildren(n1, n2, container, parentComponent);
    }
  }

  function updateComponentPreRender(instance, next) {
    instance.next = null;
    instance.vnode = next; // 这里保证vnode是最新的
    updateProps(instance.props, next.props);
    // instance.slots = next.children; // 万一用户在 setup 中解构了 slots 一直使用解构的，会导致 render 方法重新调用，获取的是老的slots
    Object.assign(instance.slots, next.children); // props 比较

    // 应该将slots里的所有属性移除掉再添加新的
  }

  function setupRenderEffect(instance, container) {
    const componentUpdateFn = () => {
      const { m, bm } = instance;
      if (!instance.isMounted) {
        // 初次渲染
        bm && invokeHooks(bm);
        const subTree = instance.render.call(instance.proxy, instance.proxy);
        instance.subTree = subTree;
        patch(null, subTree, container, null, instance); // 创建父子关系
        instance.isMounted = true;
        m && invokeHooks(m);
      } else {
        const { bu, u } = instance;
        // 组件更新,自身状态变了要更新子树
        let next = instance.next;
        if (next) {
          // 渲染前的更新
          updateComponentPreRender(instance, next);
        }
        bu && invokeHooks(bu);
        const subTree = instance.render.call(instance.proxy, instance.proxy);
        patch(instance.subTree, subTree, container, null, instance);
        instance.subTree = subTree;
        u && invokeHooks(u);
      }
    };
    const effect = new ReactiveEffect(componentUpdateFn, () => {
      queueJob(instance.update);
    });
    const update = (instance.update = effect.run.bind(effect));
    update();
  }

  function mountComponent(n2, container, anchor, parentComponent) {
    // 1) 给组件生成一个实例 instance
    const instance = (n2.component = createInstance(n2, parentComponent)); // 元素更新复用的是元素，组件更新复用的是组件实例

    if (isKeepAlive(n2)) {
      instance.ctx.renderer = {
        createElement: hostCreateElement,
        move(vnode, container) {
          hostInsert(vnode.component.subTree.el, container);
        },
        patch,
        unmount,
      };
    }

    // 2) 初始化实例属性 props attrs slots
    setupComponent(instance);
    // 3) 创建渲染effect
    setupRenderEffect(instance, container);
  }

  function shouldComponentUpdate(n1, n2) {
    const prevProps = n1.props;
    const nextProps = n2.props;

    const c1 = n1.children;
    const c2 = n2.children;
    if (Object.keys(c1).length || Object.keys(c2).length) return true;

    return hasChangedProps(prevProps, nextProps);
  }

  function patchComponent(n1, n2, container) {
    const instance = (n2.component = n1.component);
    // 在这里触发 componentUpdateFn 函数，让它去处理更新
    if (shouldComponentUpdate(n1, n2)) {
      instance.next = n2;
      instance.update();
    }
  }

  function processComponent(n1, n2, container, anchor, parentComponent) {
    if (n1 == null) {
      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
        // keep-alive 不再执行初始化逻辑
        parentComponent.ctx.activated(n2, container);
      } else {
        mountComponent(n2, container, anchor, parentComponent);
      }
    } else {
      patchComponent(n1, n2, container); // 组件的属性发生变化,插槽更新 <xx :a=3> => <xx a:4>
    }
  }

  const patch = (n1, n2, container, anchor = null, parentComponent = null) => {
    // patch 方法每次更新都会重新执行
    // n1 和 n2 不是相同节点, 直接删除掉换新的
    if (n1 && !isSameVnode(n1, n2)) {
      // n1 有值 并且 不是相同节点， 说明是更新，而不是初始化
      unmount(n1, parentComponent);
      n1 = null; // 删除之前的继续走初始化流程
    }
    const { type, shapeFlag } = n2;
    switch (type) {
      case Text:
        processText(n1, n2, container);
        break;
      case Fragment:
        processFragment(n1, n2, container, parentComponent);
        break;
      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
          processElement(n1, n2, container, anchor, parentComponent);
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          processComponent(n1, n2, container, anchor, parentComponent);
        }
    }
  };

  return {
    render(vnode, container) {
      // 根据我们的vdom和容器
      // 通过vdom创建真实dom插入到容器中
      if (vnode == null) {
        // 执行卸载的逻辑
        unmount(container._vnode, null);
      } else {
        const prevVNode = container._vnode || null; // 取到上次保存的虚拟节点
        const nextVnode = vnode; // 最新的虚拟节点
        patch(prevVNode, nextVnode, container); // 对比差异
        container._vnode = nextVnode; // 第一次渲染的虚拟节点
      }
    },
  };
}
